/*
===============================================================================

	Main class

	This is the main entrance point for the programm.

===============================================================================
*/
#include <ClanLib/core.h>
#include <ClanLib/display.h>
#include <ClanLib/gl.h>
#include <ClanLib/application.h>
#include <vector>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/lognormal_distribution.hpp>
#include <boost/random/normal_distribution.hpp>
#include "ConvexHullSimulationStepper.h"
#include "Timer.h"
#include <cmath>
#include <algorithm>

#include <fstream>		// std::ifstream

#define NUM_POINTS (42)
#define X_WIDTH (1600.0)//is a double because points are generated in double
#define Y_WIDTH (900.0)
#define APPLICATION_NAME "Divide and Conquer Convex Hull Simulation"
#define COLOR_BG clan::Colorf::white
#define COLOR_POINTS clan::Colorf::black
#define COLOR_POINTS_OF_INTEREST clan::Colorf::blue
#define COLOR_LINES clan::Colorf::black
#define RADIUS_POINTS (2.5f)
#define RADIUS_POINTS_OF_INTEREST (RADIUS_POINTS * 2.f)
#define RANDOM_POINT_DISTRIBUTION uniform_real

namespace clan
{
	struct Vec2dComparer
	{
		bool operator() (const clan::Vec2d& a, const clan::Vec2d& b){
			return a.x < b.x;
		}

	};
}

std::vector<clan::Vec2d> generateRandomPoints(unsigned numPoints, double xRnage, double yRange);
std::vector<clan::Vec2d> getPointsFromFile( std::string filename );

namespace hrt = HighResolutionTimer;

class Simulation
{
public:
	static int main(const std::vector<std::string> &args)
	{
		clan::SetupCore setup_core;
		clan::SetupDisplay setup_display;
		clan::SetupGL setup_gl;
		
		m_numPoints = NUM_POINTS;
		m_xRange = X_WIDTH;
		m_yRange = Y_WIDTH;
		//rather ceil than round because number of pixels should always rather be greater than maximum position of points than lower
		int windowWidth = static_cast<int>(std::ceil(m_xRange));
		int windowHeight = static_cast<int>(std::ceil(m_yRange));
		clan::DisplayWindow window(APPLICATION_NAME, windowWidth, windowHeight);
		clan::Canvas canvas(window);
		m_canvas = &canvas;
		m_canvasSize = canvas.get_size();

		clan::InputDevice keyboard = window.get_ic().get_keyboard();
		clan::Slot slot = keyboard.sig_key_down().connect(&m_KeyDown);
		slot.enable();

		std::vector<clan::Vec2d> points = generateRandomPoints(m_numPoints, m_xRange, m_yRange);
		//std::vector<clan::Vec2d> points = getPointsFromFile(m_filename);
		std::sort(points.begin(), points.end(), clan::Vec2dComparer());

		ConvexHullSimulationStepper simulation(points);
		m_simulationStepper = &simulation;
		while (!keyboard.get_keycode(clan::keycode_escape))
		{
			canvas.clear(COLOR_BG);
			m_DrawPoints(points);
			if(m_enterPressed)
			{
				simulation.Step();
				m_enterPressed = false;
			}
			m_DrawPoints(simulation.GetPointsOfInterest(), COLOR_POINTS_OF_INTEREST, RADIUS_POINTS_OF_INTEREST);
			m_DrawLines(simulation.GetConvexHullLines());
			canvas.flush();
			window.flip();
			clan::KeepAlive::process();
		}

		return 0;
	}


private:
	static clan::Vec2f ConvertToCanvasSpace(const clan::Vec2d& point)
	{
		return clan::Vec2f(static_cast<float>(point.x * m_canvasSize.width / m_xRange + m_canvasSize.width / 2.),
			static_cast<float>(m_canvasSize.height-(point.y * m_canvasSize.height / m_yRange + m_canvasSize.height / 2.)));
	}

	static void m_DrawLines(const std::vector<clan::Line2d> lines, const clan::Colorf& color = COLOR_LINES)
	{
		std::vector<clan::Line2d>::const_iterator it;
		for(it = lines.begin(); it != lines.end(); ++it)
		{
			clan::LineSegment2f canvasLine(ConvertToCanvasSpace(it->p), ConvertToCanvasSpace(it->q));
			m_canvas->draw_line(canvasLine, color);
		}
	}

	static void m_DrawPoints(const std::vector<clan::Vec2d> points, const clan::Colorf& color = COLOR_POINTS, float radius = RADIUS_POINTS)
	{
		std::vector<clan::Vec2d>::const_iterator it;
		for(it = points.begin(); it != points.end(); ++it)
		{
			clan::Vec2f canvasPoint = ConvertToCanvasSpace(*it);
			m_canvas->fill_circle(clan::Pointf(canvasPoint), radius, color);
		}
	}

	static void m_KeyDown(const clan::InputEvent& inputEvent)
	{
		if(inputEvent.id == clan::keycode_enter)
		{
			m_enterPressed = true;
		}
	}

	static clan::Canvas* m_canvas;
	static ConvexHullSimulationStepper* m_simulationStepper;
	static clan::Size m_canvasSize;
	static std::string m_filename;
	static unsigned m_numPoints;
	static double m_xRange;
	static double m_yRange;
	static bool m_enterPressed;
};
clan::Canvas* Simulation::m_canvas = nullptr;
ConvexHullSimulationStepper* Simulation::m_simulationStepper = nullptr;
unsigned Simulation::m_numPoints = 0;
clan::Size Simulation::m_canvasSize(0, 0);
double Simulation::m_xRange = 0;
double Simulation::m_yRange = 0;
bool Simulation::m_enterPressed = false;



//Create global application object necessary for ClanLib framework to find defined entry point.
clan::Application app(&Simulation::main);

std::vector<clan::Vec2d> generateRandomPoints( unsigned numPoints, double xRange, double yRange )
{
	static boost::mt19937 mt19937;
	const boost::RANDOM_POINT_DISTRIBUTION<double> xDistribution(-xRange / 2., xRange / 2.);
	const boost::RANDOM_POINT_DISTRIBUTION<double> yDistribution(-yRange / 2., yRange / 2.);
	static boost::variate_generator<boost::mt19937&, boost::RANDOM_POINT_DISTRIBUTION<double> > getRandX(mt19937, xDistribution);
	static boost::variate_generator<boost::mt19937&, boost::RANDOM_POINT_DISTRIBUTION<double> > getRandY(mt19937, yDistribution);
	std::vector<clan::Vec2d> randomPoints;
	randomPoints.reserve(numPoints);
	for(unsigned i = 0; i < numPoints; ++i)
	{
		randomPoints.push_back(clan::Vec2d(getRandX(), getRandY()));
	}
	return randomPoints;
}

/* ====================
getPointsFromFile

  This Function reads a specific .txt file, saves the x and y values into a
  Vec2d vector and returns it.
==================== */
std::vector<clan::Vec2d> getPointsFromFile( std::string filename )
{
	int pointX, pointY;
	std::string line;
	std::ifstream file (filename);
	std::vector<clan::Vec2d> pointsFromFile;

	if (file.is_open()) {
		while( getline(file, line) != NULL ) {
			pointX = atoi( ( line.substr(0, line.find(',')) ).c_str() );
			pointY = atoi( ( line.substr(line.find(',') + 1) ).c_str() );
			pointsFromFile.push_back(clan::Vec2d( pointX, pointY ));
		}
	}

	return pointsFromFile;
}